import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { Repository, SelectQueryBuilder } from 'typeorm';
import { InjectRepository } from '@nestjs/typeorm';
import {
  CreateCategoryDto,
  DeleteCategoryDto,
  QueryCategoryDto,
  UpdateCategoryDto,
} from '@/modules/article/article_category/dto/dto';
import { ArticleCategory } from '@/modules/article/article_category/entities/category.entity';
import { Article } from '@/modules/article/entities/article.entity';
import { SharedService } from '@/shared/shared.service';
import { DataTableOperatorFilterMetaData, QueryListDto } from '@/modules/article/dto/dto';

@Injectable()
export class ArticleCategoryService {
  constructor(
    @InjectRepository(ArticleCategory)
    private readonly categoryRepository: Repository<ArticleCategory>,
    @InjectRepository(Article)
    private articleRepository: Repository<Article>,
    private readonly sharedService: SharedService,
  ) {}

  async categoryList(data: QueryCategoryDto) {
    const [list, total] = await this.categoryRepository.findAndCount({
      skip: (data.pageNo - 1) * data.pageSize,
      take: data.pageSize,
      order: { update_date: data.sort ?? 'DESC' },
    });
    return {
      list,
      total,
      pageNo: data.pageNo,
      pageSize: data.pageSize,
    };
  }

  async categoryFindOne(id: number) {
    return await this.categoryRepository.findOne({ where: { category_id: id } });
  }

  async categoryArticle(data: QueryCategoryDto, id: number) {
    const [list, total] = await this.articleRepository.findAndCount({
      skip: (data.pageNo - 1) * data.pageSize,
      take: data.pageSize,
      relations: { tags: true, categories: true, author: true },
      where: { categories: { category_id: id } },
      order: { update_date: data.sort ?? 'DESC' },
    });
    return {
      list,
      total,
      pageNo: data.pageNo,
      pageSize: data.pageSize,
    };
  }

  async createCategory(data: CreateCategoryDto) {
    // 检查分类名称是否已存在
    const isExist = await this.categoryRepository.findOne({
      where: { category_name: data.name },
    });
    if (isExist) {
      throw new HttpException('分类名称已存在', HttpStatus.BAD_REQUEST);
    }

    // 生成别名
    const slug = await this.sharedService.createSlug(data.name, async (slug) => {
      const isExist = await this.categoryRepository.findOne({
        where: { category_slug: slug },
      });
      return !!isExist;
    });

    return this.categoryRepository.save({
      category_name: data.name,
      category_description: data.description,
      category_slug: slug,
    });
  }

  async updateCategory(id: number, data: UpdateCategoryDto) {
    // 检查标签是否存在
    const category = await this.categoryRepository.findOne({
      where: { category_id: id },
    });
    if (!category) {
      throw new HttpException('分类不存在', HttpStatus.BAD_REQUEST);
    }

    // 检查分类名称是否已存在
    if (data.name) {
      const isExist = await this.categoryRepository.findOne({
        where: { category_name: data.name },
      });
      if (isExist) {
        throw new HttpException('分类名称已存在', HttpStatus.BAD_REQUEST);
      }
    }

    // 检查分类别名是否已存在
    if (data.slug) {
      const isExist = await this.categoryRepository.findOne({
        where: { category_slug: data.slug },
      });
      if (isExist) {
        throw new HttpException('分类别名已存在', HttpStatus.BAD_REQUEST);
      }
    }

    return this.categoryRepository.update(
      { category_id: id },
      {
        category_name: data.name,
        category_description: data.description,
        category_slug: data.slug,
      },
    );
  }

  async removeCategory(data: DeleteCategoryDto) {
    return this.categoryRepository.delete(data.ids);
  }

  async removeCategoryOne(id: number) {
    return this.categoryRepository.delete(id);
  }

  async categoryTotal() {
    return await this.categoryRepository.count();
  }

  async categoryArticleTotal(id: number) {
    return await this.articleRepository.count({
      where: { categories: { category_id: id } },
    });
  }

  async articleCategoryQuery({ lazyParams }: QueryListDto) {
    const pageSize = lazyParams.rows;
    const pageNo = 'page' in lazyParams ? lazyParams.page : 0;

    const queryBuilder = this.categoryRepository.createQueryBuilder('categories');

    // 处理过滤
    this.handleFilters(queryBuilder, lazyParams.filters);

    // 处理排序
    this.handleSort(
      queryBuilder,
      lazyParams.sortField || 'category_id', // 没有排序字段用 id 排序
      lazyParams.sortOrder === -1 ? 'DESC' : 'ASC',
    );

    // 分页
    queryBuilder.skip(pageNo * pageSize);
    queryBuilder.take(pageSize);
    const [list, total] = await queryBuilder.getManyAndCount();
    return {
      list,
      total,
      pageNo,
      pageSize,
    };
  }

  private handleFilters(
    queryBuilder: SelectQueryBuilder<ArticleCategory>,
    filters: Record<string, DataTableOperatorFilterMetaData>,
  ) {
    // 循环 filters 对象
    for (const key of Object.keys(filters)) {
      const filter = filters[key];

      // 如果 value 为 null 则跳过
      if (filter.value === null) continue;

      // 处理 key=global
      if (key === 'global') {
        switch (filter.matchMode) {
          case 'contains':
            // 全局模糊匹配 key: global
            // 全局模糊匹配，匹配 category_name category_description category_slug
            queryBuilder
              .orWhere(`categories.category_name LIKE :value`, {
                value: `%${filter.value}%`,
              })
              .orWhere(`categories.category_description LIKE :value`, {
                value: `%${filter.value}%`,
              })
              .orWhere(`categories.category_slug LIKE :value`, {
                value: `%${filter.value}%`,
              });
            break;
        }
        continue;
      }

      // 处理其他 key
      // 根据 matchMode 进行匹配
      switch (filter.matchMode) {
        case 'contains':
          // 模糊匹配 包含
          queryBuilder.andWhere(`${key} LIKE :value`, {
            value: `%${filter.value}%`,
          });
          break;
        case 'dateIs':
          // 日期类型的匹配 只匹配日期
          const date = new Date(filter.value);
          const startOfDay = new Date(date.getFullYear(), date.getMonth(), date.getDate());
          const endOfDay = new Date(
            date.getFullYear(),
            date.getMonth(),
            date.getDate(),
            23,
            59,
            59,
            999,
          );
          queryBuilder.andWhere(`categories.${key} BETWEEN :start AND :end`, {
            start: startOfDay,
            end: endOfDay,
          });
          break;
      }
    }
  }

  private handleSort(
    queryBuilder: SelectQueryBuilder<ArticleCategory>,
    sortField: string,
    sortOrder: 'ASC' | 'DESC',
  ) {
    queryBuilder.orderBy(`categories.${sortField}`, sortOrder);
  }
}
